﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Aeon.Emulator.Interrupts
{
    /// <summary>
    /// Provides real-time-clock services (int 1Ah).
    /// </summary>
    internal sealed class RealTimeClockHandler : IInterruptHandler
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the RealTimeClockHandler class.
        /// </summary>
        public RealTimeClockHandler()
        {
        }
        #endregion

        #region Public Methods
        IEnumerable<InterruptHandlerInfo> IInterruptHandler.HandledInterrupts
        {
            get { return new InterruptHandlerInfo[] { 0x1A }; }
        }
        void IInterruptHandler.HandleInterrupt(int interrupt)
        {
            var now = DateTime.Now;
            vm.Processor.CarryFlag = false;

            switch(vm.Processor.AH)
            {
            case ReadClock:
                // This should be nonzero if timer has run for more than 24 hours.
                // Ignore it for now.
                vm.Processor.AL = 0;

                var nowSpan = now.TimeOfDay;
                uint dosTicks = (uint)(nowSpan.TotalMilliseconds / 55.0);
                vm.Processor.DX = (short)(dosTicks & 0xFFFF);
                vm.Processor.CX = (short)((dosTicks >> 16) & 0xFFFF);
                break;

            case GetDate:
                vm.Processor.CX = (short)ConvertToBCD(now.Year);
                vm.Processor.DL = (byte)ConvertToBCD(now.Day);
                vm.Processor.DH = (byte)ConvertToBCD(now.Month);
                break;

            default:
                System.Diagnostics.Debug.WriteLine(string.Format("Timer function {0:X2}h not implemented.", vm.Processor.AH));
                break;
            }

            SaveFlags(EFlags.Carry);
        }
        #endregion

        #region IVirtualDevice Members
        void IVirtualDevice.Pause()
        {
            this.timer.Change(Timeout.Infinite, Timeout.Infinite);
        }
        void IVirtualDevice.Resume()
        {
            this.timer.Change(0, 55);
        }
        /// <summary>
        /// Invoked when the virtual device has been added to a VirtualMachine.
        /// </summary>
        /// <param name="vm">VirtualMachine which owns the device.</param>
        void IVirtualDevice.DeviceRegistered(VirtualMachine vm)
        {
            this.vm = vm;
            this.timer = new Timer(UpdateClock, null, 0, 55);
        }
        /// <summary>
        /// Invoked when the device should save its current state to a <see cref="System.IO.Stream"/>.
        /// </summary>
        /// <param name="destination"><see cref="System.IO.Stream"/> to which the state is saved.</param>
        void IVirtualDevice.SaveCurrentState(Stream destination)
        {
        }
        /// <summary>
        /// Invoked when the device should restore its state from a <see cref="System.IO.Stream"/>.
        /// </summary>
        /// <param name="source"><see cref="System.IO.Stream"/> from which the state is restored.</param>
        void IVirtualDevice.RestoreState(Stream source)
        {
        }
        #endregion

        #region IDisposable Members
        void IDisposable.Dispose()
        {
            this.timer.Dispose();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Updates the emulated BIOS clock with the number of 55 msec ticks since midnight.
        /// </summary>
        /// <param name="state">Unused state object.</param>
        private void UpdateClock(object state)
        {
            vm.PhysicalMemory.Bios.RealTimeClock = (uint)(DateTime.Now.TimeOfDay.TotalMilliseconds / 55.0);
        }
        private void SaveFlags(EFlags modified)
        {
            var oldFlags = (EFlags)vm.PhysicalMemory.GetUInt16(vm.Processor.SS, (ushort)(vm.Processor.SP + 4));
            oldFlags &= ~modified;
            vm.PhysicalMemory.SetUInt16(vm.Processor.SS, (ushort)(vm.Processor.SP + 4), (ushort)(oldFlags | (vm.Processor.Flags & modified)));
        }
        /// <summary>
        /// Converts an integer value to BCD representation.
        /// </summary>
        /// <param name="value">Integer value to convert.</param>
        /// <returns>BCD representation of the value.</returns>
        private int ConvertToBCD(int value)
        {
            int result = 0;
            int byteCount = 0;

            while(value != 0)
            {
                int digit = value % 10;
                value /= 10;
                digit |= (value % 10) << 4;
                value /= 10;

                result |= digit << (byteCount * 8);
                byteCount++;
            }

            return result;
        }
        #endregion

        #region Private Fields
        private VirtualMachine vm;
        private Timer timer;
        #endregion

        #region Private Constants
        private const byte ReadClock = 0;
        private const byte SetClock = 1;
        private const byte GetDate = 4;
        #endregion
    }
}
